package mygame;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.app.SimpleApplication;
import com.jme3.audio.AudioNode;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.collision.CollisionResults;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.shapes.EmitterSphereShape;
import com.jme3.font.BitmapText;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.post.filters.CartoonEdgeFilter;
import com.jme3.renderer.Camera;
import com.jme3.renderer.Caps;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
import java.util.ArrayList;
import java.util.List;

/**
 * A walking animated character followed by a 3rd person camera on a terrain with LOD.
 * @author normenhansen
 */
public class Main extends SimpleApplication implements ActionListener, PhysicsCollisionListener, AnimEventListener {
private Ray ray = new Ray();
    AbstractHeightMap heightmap = null;
    private RigidBodyControl landscape;
    private BulletAppState bulletAppState;
    //character
    CharacterControl character;
    Spatial model;
    //temp vectors
    Vector3f walkDirection = new Vector3f();
    Vector3f cuboDirection = new Vector3f();
    //terrain
    TerrainQuad terrain;
    RigidBodyControl terrainPhysicsNode;
    //Materials
    Material matRock;
    Material matBullet;
    Material matCaja;
    //animation
    AnimChannel animationChannel;
    AnimChannel shootingChannel;
    AnimControl animationControl;
    private Material mat_terrain;
    float airTime = 0;
    Spatial nodo;
    //camera
    boolean left = false, right = false, up = false, down = false;
    ChaseCamera chaseCam;
    //bullet
    Sphere bullet;
    boolean cuboBool=false;
    Geometry cubo;
    SphereCollisionShape bulletCollisionShape;
    //explosion
    ParticleEmitter effect;
    //brick wall
    Box brick;
    float bLength = .8f;
    float bWidth = .8f;
    float bHeight = .8f;
    FilterPostProcessor fpp;
private BasicShadowRenderer bsr;
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }
    private CollisionResults results;
    private Node cubos;
    private Geometry cuboF;
    private AudioNode audio_gun;
    private AudioNode audio_nature;
    private BitmapText hudText;
 public void makeToonish(Spatial spatial) {
    if (spatial instanceof Node) {
      Node n = (Node) spatial;
      for (Spatial child : n.getChildren()) {
        makeToonish(child);
      }
    } else if (spatial instanceof Geometry) {
      Geometry g = (Geometry) spatial;
      Material m = g.getMaterial();
      if (m.getMaterialDef().getName().equals("Phong Lighting")) {
        Texture t = assetManager.loadTexture("Textures/toon.png");
        m.setTexture("ColorRamp", t);
        m.setBoolean("UseMaterialColors", true);
        m.setColor("Specular", ColorRGBA.Black);
        m.setColor("Diffuse", ColorRGBA.White);
        m.setBoolean("VertexLighting", true);
      }
    }
  }
    @Override
    public void simpleInitApp() {
        brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);

if (renderer.getCaps().contains(Caps.GLSL100)) {
      FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
      CartoonEdgeFilter toon = new CartoonEdgeFilter();
      toon.setEdgeColor(ColorRGBA.Black);
      toon.setEdgeWidth(0.5f);
        toon.setEdgeIntensity(10.0f);
        toon.setNormalThreshold(10.8f);
      fpp.addFilter(toon);
      viewPort.addProcessor(fpp);
    }

bsr = new BasicShadowRenderer(assetManager, 1024);
    bsr.setDirection(new Vector3f(.5f, -.5f, -.5f).normalizeLocal());
    viewPort.addProcessor(bsr);
        setupKeys();
        createLight();
        createSky();
        createTerrain();
        prepareBullet();
        prepareEffect();
        createCharacter();
        setupChaseCamera();
        setupFilter();
        initGui();
        makeToonish(rootNode);
        hacerCubos(new Vector3f(-130, 100, -30));
        hacerCuboFantasma(new Vector3f(-120, 100, -20));
        bulletAppState.getPhysicsSpace().add(terrain);
        bulletAppState.getPhysicsSpace().add(character);



    }

    private void setupFilter() {
        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
        fpp.addFilter(bloom);
        viewPort.addProcessor(fpp);
    }

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    private void setupKeys() {
        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
        inputManager.addListener(this, "wireframe");
        inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_V));
        inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("pick target", new KeyTrigger(KeyInput.KEY_G));
        inputManager.addListener(this, "pick target");
        inputManager.addListener(this, "CharLeft");
        inputManager.addListener(this, "CharRight");
        inputManager.addListener(this, "CharUp");
        inputManager.addListener(this, "CharDown");
        inputManager.addListener(this, "CharSpace");
        inputManager.addListener(this, "CharShoot");
    }

    private void prepareBullet() {
        bullet = new Sphere(32, 32, 0.4f, true, false);
        bullet.setTextureMode(TextureMode.Projected);
        bulletCollisionShape = new SphereCollisionShape(0.4f);
        matBullet = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        matBullet.setColor("Color", ColorRGBA.Blue);
        matBullet.setColor("m_GlowColor", ColorRGBA.Green);
        getPhysicsSpace().addCollisionListener(this);
    }

    private void hacerCubos(Vector3f location) {
        cubos= new Node("cubos");
        rootNode.attachChild(cubos);
        cubo = new Geometry("cubo", brick);
        cubo.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(bHeight, bLength, bWidth)),100.5f));
        matCaja= new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
           Texture wood=assetManager.loadTexture("Textures/madera.jpg");
        matCaja.setTexture("ColorMap", wood);
        matCaja.setColor("Color", ColorRGBA.Brown);
           cubo.setMaterial(matCaja);
        cubo.setLocalTranslation(location);
        cubo.setShadowMode(ShadowMode.CastAndReceive);
        
        cubo.getControl(RigidBodyControl.class).setFriction(0.6f);
        this.cubos.attachChild(cubo);
        this.getPhysicsSpace().add(cubo);
        



    }
    public void hacerCuboFantasma(Vector3f pos){
        Box boxer= new Box(5, 5, 5);
        cuboF= new Geometry("CuboFantasma",boxer);
        cuboF.addControl(new RigidBodyControl(0f));
        
        
        matCaja= new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
           Texture wood=assetManager.loadTexture("Textures/madera.jpg");
        matCaja.setTexture("ColorMap", wood);
        matCaja.setColor("Color", ColorRGBA.Brown);
           cuboF.setMaterial(matCaja);
        cuboF.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-179,-41,-161));
        cuboF.setShadowMode(ShadowMode.CastAndReceive);
        
        
        this.cubos.attachChild(cuboF);
        this.getPhysicsSpace().add(cuboF);
    }
    private void initGui(){
        hudText = new BitmapText(guiFont, false);          
hudText.setSize(guiFont.getCharSet().getRenderedSize());      // font size
hudText.setColor(ColorRGBA.Blue);                             // font color
hudText.setText(String.valueOf(cuboBool)+"     "+terrain.getControl(RigidBodyControl.class).getPhysicsLocation().toString());             // the text
hudText.setLocalTranslation(300, hudText.getLineHeight(), 0); // position
guiNode.attachChild(hudText);
    }
    private void prepareEffect() {
        int COUNT_FACTOR = 1;
        float COUNT_FACTOR_F = 1f;
        effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR);
        effect.setSelectRandomImage(true);
        effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F)));
        effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
        effect.setStartSize(1.3f);
        effect.setEndSize(20f);
        effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
        effect.setParticlesPerSec(0);
        effect.setGravity(0, -5, 0);
        effect.setLowLife(.4f);
        effect.setHighLife(.5f);
        effect.setInitialVelocity(new Vector3f(0, 7, 0));
        effect.setVelocityVariation(1f);
        effect.setImagesX(2);
        effect.setImagesY(2);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
     
        mat.setTexture("Texture", assetManager.loadTexture("Textures/flame.png"));
        effect.setMaterial(mat);
//        effect.setLocalScale(100);
        rootNode.attachChild(effect);
    }

    private void createLight() {
         DirectionalLight dl = new DirectionalLight();
    dl.setDirection(new Vector3f(.5f, -.5f, -.5f));
    rootNode.addLight(dl);
    }

    private void createSky() {
        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/FullskiesBlueClear03.dds", false));
    }

    private void createTerrain() {
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");

        /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/alphamap.png"));

        /** 1.2) Add GRASS texture into the red layer (Tex1). */
        Texture grass = assetManager.loadTexture(
                "Textures/grass_toon.jpg");
        grass.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 64f);

        /** 1.3) Add DIRT texture into the green layer (Tex2) */
        Texture dirt = assetManager.loadTexture(
                "Textures/cracked-earth-texture.jpg");
        dirt.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", dirt);
        mat_terrain.setFloat("Tex2Scale", 32f);

        /** 1.4) Add ROAD texture into the blue layer (Tex3) */
        Texture rock = assetManager.loadTexture(
                "Textures/cartoon1M.jpg");
        rock.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 128f);

        /** 2. Create the height map */
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/mountains_512_0.png");
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

        /** 3. We have prepared material and heightmap. 
         * Now we create the actual terrain:
         * 3.1) Create a TerrainQuad and name it "my terrain".
         * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
         * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
         * 3.4) As LOD step scale we supply Vector3f(1,1,1).
         * 3.5) We supply the prepared heightmap itself.
         */
        terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());

        /** 4. We give the terrain its material, position & scale it, and attach it. */
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, -100, 0);
        terrain.setLocalScale(2f, 1f, 2f);
        terrain.setShadowMode(ShadowMode.CastAndReceive);

        rootNode.attachChild(terrain);

        /** 5. The LOD (level of detail) depends on were the camera is: */
        List<Camera> cameras = new ArrayList<Camera>();
        cameras.add(getCamera());
        TerrainLodControl control = new TerrainLodControl(terrain, cameras);
        terrain.addControl(control);

        /** 6. Add physics: */
        // We set up collision detection for the scene by creating a
        // compound collision shape and a static RigidBodyControl with mass zero.*/
        CollisionShape terrainShape =
                CollisionShapeFactory.createMeshShape((Node) terrain);
        landscape = new RigidBodyControl(terrainShape, 0f);
        terrain.addControl(landscape);
    }

    private void createCharacter() {
        CapsuleCollisionShape capsule = new CapsuleCollisionShape(1f, 1f);
        character = new CharacterControl(capsule, 0.01f);
        model = (Node) assetManager.loadModel("Models/Cone.j3o");
        model.setName("player");
        model.setShadowMode(ShadowMode.CastAndReceive);

        //model.setLocalScale(0.5f);
        model.addControl(character);
        
        character.setPhysicsLocation(new Vector3f(-140, 50, -10));
        character.setJumpSpeed(20f);
        
        rootNode.attachChild(model);
        
        getPhysicsSpace().add(character);
    }

    private void setupChaseCamera() {
        flyCam.setEnabled(false);
        chaseCam = new ChaseCamera(cam, model, inputManager);
        chaseCam.setDragToRotate(true);
        chaseCam.setMaxDistance(40f);
        chaseCam.setMinDistance(40f);
    }


    @Override
    public void simpleUpdate(float tpf) {
        hudText.setText(String.valueOf(cuboBool)+"     "+character.getPhysicsLocation().toString());             // the text

          // 1. Reset results list.
    CollisionResults results = new CollisionResults();
    // 2. Aim the ray from camera location in camera direction.
    ray.setOrigin(character.getPhysicsLocation());
    ray.setDirection(cam.getDirection());
    // 3. Collect intersections between ray and all nodes in results list.
    rootNode.collideWith(ray, results);
    // 4. Use the result
    if (results.size() > 0) {
      // The closest result is the target that the player picked:
      Geometry target = results.getClosestCollision().getGeometry();
      // if the target is one of the chosen ones affected by this control...
      if (target.getName().equals("cubo")) {
          Vector3f pos= new Vector3f(character.getPhysicsLocation().x+1,character.getPhysicsLocation().y+1,character.getPhysicsLocation().z+1);
                target.getControl(RigidBodyControl.class).setPhysicsLocation(pos);
                target.getControl(RigidBodyControl.class).activate();
                
                
//character.setPhysicsLocation(new Vector3f(-120,950,-10));
        // if camera closer than 10...
        if (character.getPhysicsLocation().distance(target.getLocalTranslation()) < 10) {
            System.out.println("detectado el "+ target.getName()+" a la distancia de :"+character.getPhysicsLocation().distance(target.getLocalTranslation()));
            
             
                    
                    // ... move the cube in the direction that camera is facing
                    //spatial.setLocalTranslation(spatial.getLocalTranslation().
                    //     addLocal(cam.getDirection().normalizeLocal()));
        }
      }
    }
        
        
        Vector3f camDir = cam.getDirection().clone().multLocal(1f);
        Vector3f camLeft = cam.getLeft().clone().multLocal(1f);
        camDir.y = 0;
        camLeft.y = 0;
        walkDirection.set(0, 0, 0);
        if (left) {
            walkDirection.addLocal(camLeft);
           
        }
        if (right) {
            walkDirection.addLocal(camLeft.negate());
            

        }
        if (up) {
            walkDirection.addLocal(camDir);
            

        }
        if (down) {
            walkDirection.addLocal(camDir.negate());
            
        }
        if (!character.onGround()) {
            airTime = airTime + tpf;
        } else {
            airTime = 0;
        }
        character.setWalkDirection(walkDirection);
        if(cuboBool){
        Vector3f pos= new Vector3f(character.getPhysicsLocation().x+3,character.getPhysicsLocation().y+1,character.getPhysicsLocation().z);
                rootNode.getChild("cubo").getControl(RigidBodyControl.class).setPhysicsLocation(pos);
                rootNode.getChild("cubo").getControl(RigidBodyControl.class).clearForces();
                  rootNode.getChild("cubo").getControl(RigidBodyControl.class).activate();
        }
    }

    public void onAction(String binding, boolean value, float tpf) {
        if (binding.equals("CharLeft")) {
            if (value) {
                left = true;
            } else {
                left = false;
            }
        } else if (binding.equals("CharRight")) {
            if (value) {
                right = true;
            } else {
                right = false;
            }
        } else if (binding.equals("CharUp")) {
            if (value) {
                up = true;
            } else {
                up = false;
            }
        } else if (binding.equals("CharDown")) {
            if (value) {
                down = true;
            } else {
                down = false;
            }
        } else if (binding.equals("CharSpace")) {
            character.jump();
        } else if (binding.equals("CharShoot") && !value) {
            bulletControl();
        }else if(binding.equals("pick target")){
//             if(cuboBool){
//                cuboBool=false;
//            }else if(!cuboBool){
//                  cuboBool=true;}
//        
            cuboBool=!cuboBool;
        }
    }

    private void bulletControl() {
        Geometry bulletg = new Geometry("bullet", bullet);
        bulletg.setMaterial(matBullet);
        bulletg.setShadowMode(ShadowMode.CastAndReceive);
        bulletg.setLocalTranslation(character.getPhysicsLocation().add(character.getWalkDirection().mult(5)));
        RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1);
        bulletControl.setCcdMotionThreshold(0.1f);
        bulletControl.setLinearVelocity(character.getWalkDirection().mult(80));
        bulletg.addControl(bulletControl);
        rootNode.attachChild(bulletg);
        getPhysicsSpace().add(bulletControl);
    }

    public void updateCollision(PhysicsCollisionEvent event) {
        final Spatial node = event.getNodeA();
        final Spatial nodeb = event.getNodeB();
        if (nodeb.getName() == "player") {
            System.out.println(nodeb.getName() + " he colisionado");
        }

        
        if (node.getName() == "cubo" && nodeb.getName() == "bullet") {
            System.out.println(node.getName() + " he colisionado");

            //this.rootNode.detachChildNamed(node.getName());
            this.rootNode.getChild(node.getName()).setLocalTranslation(character.getPhysicsLocation().x+1,character.getPhysicsLocation().y+1,character.getPhysicsLocation().z+1);

//            this.rootNode.getChild(node.getName()).move(0, 10, 0);
//            this.rootNode.getChild(node.getName()).getWorldBound();

        }
    }
 private void initAudio() {
    /* gun shot sound is to be triggered by a mouse click. */
    audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false);
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);
 
    /* nature sound - keeps playing in a loop. */
    audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", false);
    audio_nature.setLooping(true);  // activate continuous playing
    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }
 
    public void collision(PhysicsCollisionEvent event) {
        if (event.getObjectA() instanceof BombControl) {
            final Spatial node = event.getNodeA();
            effect.killAllParticles();
            effect.setLocalTranslation(node.getLocalTranslation());
            effect.emitAllParticles();
        } else if (event.getObjectB() instanceof BombControl) {
            final Spatial node = event.getNodeB();
            effect.killAllParticles();
            effect.setLocalTranslation(node.getLocalTranslation());
            effect.emitAllParticles();
        }

        final Spatial node = event.getNodeA();
        final Spatial nodeb = event.getNodeB();

        



    }

    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
    }

    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    private AnalogListener analogListener = new AnalogListener() {

        public void onAnalog(String name, float intensity, float tpf) {
            if (name.equals("pick target")) {
//               
//            if(cuboBool){
//                cuboBool=false;
//            }else if(!cuboBool){
//                  cuboBool=true;}
               
            } // else if ...
        }
    };
}